package ar.com.e2solver.solver.operator.mutation.impl;

import ar.com.e2solver.exception.BusinessException;
import ar.com.e2solver.model.Coordinate;
import ar.com.e2solver.model.Puzzle;
import ar.com.e2solver.solver.Individual;
import ar.com.e2solver.solver.RandomConstants;
import ar.com.e2solver.solver.operator.mutation.MutationOperator;

public final class RegionRotateMutationOperator implements MutationOperator {

	private static RegionRotateMutationOperator instance = new RegionRotateMutationOperator();
	private static final int PROPORTION = 2;
	
	private RegionRotateMutationOperator(){}
	
	public static RegionRotateMutationOperator getInstance() {
		return instance;
	}
	
	public Individual operate(Individual individual) throws BusinessException {
		Puzzle puzzle = individual.getPuzzle().clone();
		
		Coordinate initialCoordinate = getInitialCoordinate(puzzle.getRowsNumber(), puzzle.getColumnNumbers());
		Coordinate finalCoordinate = getFinalCoordinate(initialCoordinate, puzzle.getRowsNumber(), puzzle.getColumnNumbers());
		
		while(initialCoordinate.getX() <= finalCoordinate.getX()){
			rotateExternal(puzzle, initialCoordinate, finalCoordinate);
			initialCoordinate = new Coordinate(initialCoordinate.getX() + 1, initialCoordinate.getY() + 1);
			finalCoordinate = new Coordinate(finalCoordinate.getX() - 1, finalCoordinate.getY() - 1);
		}
		
		return new Individual(puzzle);
	}

	private void rotateExternal(Puzzle result, Coordinate initialCoordinate, Coordinate finalCoordinate) throws BusinessException {
		int max = finalCoordinate.getX() - initialCoordinate.getX();
		if(max == 0){
			result.getPieceAt(initialCoordinate).rotateToRight(1);
		} else {
			Coordinate topCoordinate, rightCoordinate, bottomCoordinate, leftCoordinate; 
			for(int i = 0; i < max; i++){
				topCoordinate = new Coordinate(initialCoordinate.getX(), initialCoordinate.getY() + i);
				rightCoordinate = new Coordinate(initialCoordinate.getX() + i, finalCoordinate.getY());
				bottomCoordinate = new Coordinate(finalCoordinate.getX(), finalCoordinate.getY() - i);
				leftCoordinate = new Coordinate(finalCoordinate.getX() - i, initialCoordinate.getY());
				
				result.getPieceAt(topCoordinate).rotateToRight(1);
				result.getPieceAt(rightCoordinate).rotateToRight(1);
				result.getPieceAt(bottomCoordinate).rotateToRight(1);
				result.getPieceAt(leftCoordinate).rotateToRight(1);
				
				result.swapPositions(topCoordinate, rightCoordinate);
				result.swapPositions(topCoordinate, bottomCoordinate);
				result.swapPositions(topCoordinate, leftCoordinate);
			}
		}
		
	}

	/**
	 *  Busca coordenada inicial. Elige cualquier (x, y) menos de la ultima fila y ultima columna
	 *  menos de la primer fila y columna y menos de la anteultima fila y columna
	 * @param rowsNumber
	 * @param columnNumbers
	 * @return
	 */
	private Coordinate getInitialCoordinate(int rowsNumber, int columnNumbers) {
		int regionSize = (int) Math.ceil(Math.min(rowsNumber, columnNumbers) / PROPORTION) - 1;
	
		Coordinate initialCoordinate = RandomConstants.getRandomCoordinate(rowsNumber - 2 - regionSize, columnNumbers - 2 - regionSize);
		initialCoordinate = new Coordinate(initialCoordinate.getX() + 1, initialCoordinate.getY() + 1);
		
		return initialCoordinate;
	}

	/**
	 * Busca la coordenada final de la region. La coordenada final tiene 
	 * que estar en el minimo entre el final de rompecabezas - 1 y 8.
	 * 
	 * @param initialCoordinate
	 * @return
	 */
	private Coordinate getFinalCoordinate(Coordinate initialCoordinate, int rows, int cols) {
		int regionSize = (int) Math.ceil(Math.min(rows, cols) / PROPORTION);
		return new Coordinate(initialCoordinate.getX() + regionSize - 1, initialCoordinate.getY() + regionSize - 1);
	}

}
